home *** CD-ROM | disk | FTP | other *** search
/ TOS Silver 2000 / TOS Silver 2000.iso / programm / Mm2_txt / MODULA4F.TXT < prev    next >
Encoding:
Text File  |  1997-02-08  |  32.9 KB  |  740 lines

  1. 4.1  Assembler: Grundlagen und Syntax                                   4 -  1
  2. ________________________________________________________
  3.  
  4.  
  5. 4.  Der  68000 Assembler
  6.  
  7. Die Megamax Modula-Implementation erlaubt Ihnen, innerhalb von Modula-Anwei~
  8. sungen Programmteile in 68000-Assembler einzufügen. Dazu ist im Compiler
  9. ein kompletter symbolischer Ein-Pass-Assembler integriert. Auf alle Datenstruk~
  10. turen und Prozeduren, die in Modula deklariert wurden, kann über die jeweiligen
  11. Bezeichner zugegriffen werden.
  12.  
  13. Die folgende Beschreibung des Assemblers setzt voraus, daß Sie die 68000
  14. CPU in Assembler programmieren können. Dargestellt wird hier vor allem das
  15. Zusammenspiel von Assembler- und Modula-Programmteilen. Dabei werden Sie
  16. auch einiges über das 'Innenleben' des Modula-Compilers erfahren. Wenn Sie
  17. die Assemblerprogrammierung erst lernen wollen, sollten Sie eines der zahlreichen
  18. Lehrbücher  konsultieren,  z.  B.  Kane/Hawkins/Leventhal:  68000  Assembly
  19. Language Programming, Osborne/McGraw-Hill 1981
  20.  
  21.  
  22. 4.1  Grundlagen  und  Syntax
  23.  
  24. Egal, ob Sie komplette Assembler-Prozeduren mit allen Raffinessen program~
  25. mieren wollen, oder nur mal einen Systemaufruf in drei Zeilen  brauchen  -
  26. diesen Abschnitt sollten Sie in jedem Fall lesen. (Danach trennen sich dann die
  27. Wege: Gelegenheits-Assemblerprogrammierer bitte in Abschnitt 4.2, Experten
  28. in 4.3 weiterlesen!)
  29.  
  30.  
  31. Assembler-Anweisungen in Modula-Programmen
  32.  
  33. Der  Megamax-Compiler  erweitert  die  Modula-Syntax  um  die  zusätzliche
  34. Anweisung ASSEMBLER, die aus dem SYSTEM-Modul importiert werden muß.
  35. Die Syntax lautet
  36.  
  37.    Statement = .. | ASSEMBLER  AsmStatement  END | ..
  38.  
  39. Überall, wo eine Modula-Anweisung erwartet wird, kann also ein Block  von
  40. Assembler-Anweisungen eingeschoben werden. Wie ein AsmStatement genau
  41. aussieht, erfahren Sie im folgenden Abschnitt. Vieles sehen Sie sicher schon
  42. dem folgenden Beispiel an, das die bitweise Spiegelung einer CARDINAL-Variablen
  43. ('Bit Reversal') demonstriert:
  44. 4.1  Assembler: Grundlagen und Syntax                                   4 -  2
  45. ________________________________________________________
  46.  
  47.  
  48.     FOR k := 0 TO kMax DO
  49.                                                  :
  50.          ASSEMBLER               ; *** kRev  = BitReversal (k)
  51.                MOVE  k, D0        ; hole Schleifenindex
  52.                MOVEQ #15, D1      ; 16 Bit sind umzukehren
  53.          lp    LSR     #1, D0       ; schiebe Bit ins eXtend-Flag
  54.                ROXL   #1, D2       ; .. und von dort ins Zielregister
  55.                DBF     D1, lp        ; das Ganze 16 mal
  56.                MOVE  D2, kRev    ; schreibe Ergebnis in die Variable kRev
  57.          END;
  58.          ...
  59.     END (* FOR k *)
  60.  
  61.  
  62. Aufbau von Assembleranweisungen
  63.  
  64. Der  Aufbau  einer  Assembleranweisung  ist  Ihnen  vermutlich  von  einem  der
  65. konventionellen separaten Assembler bekannt:
  66.  
  67.    AsmStatement =  Label   Opcode  Operand    ';' Comment .
  68.  
  69. Zu den einzelnen Komponenten des AsmStatement finden Sie in den folgenden
  70. Abschnitten dieses Kapitels nähere Informationen.
  71.  
  72. Zu  beachten  ist,  daß  die  Assembler-Instruktionen  und  die  Registernamen
  73. immer in Großbuchstaben geschrieben sein müssen! Es ist nicht möglich, nur
  74. die Assembler-Blöcke mit der $C-Option zu klammern, um sie klein schreiben
  75. zu können. Aufgrund interner  Gegebenheiten  im  Compiler  würden  Sie  dann
  76. Schwierigkeiten haben, auf Variablen und Funktionen zuzugreifen. Statt dessen
  77. müßten Sie dann schon das ganze Modul mit $C- übersetzen.
  78.  
  79. Übliche Assembler erwarten, daß Sie ein AsmStatement pro Textzeile in Ihren
  80. Quelltext schreiben. Der integrierte Megamax-Assembler paßt sich hier den
  81. Modula-Gepflogenheiten an: Er ist formatfrei; Sie können also ein AsmStatement
  82. über mehrere Zeilen verteilen oder auch mehrere Statements auf einer Zeile
  83. zusammenfassen. Eine Besonderheit ist allerdings zu beachten: Ein ';'  leitet
  84. einen Kommentar ein, der stets bis zum Zeilenende reicht.
  85.  
  86. Außer der Kommentierung nach ';' ist auch in Assemblerteilen die Verwendung
  87. der Modula-Kommentarklammern (* *) möglich. Sie erlaubt einfaches Ausklam~
  88. mern ganzer Programmteile.
  89. 4.1  Assembler: Grundlagen und Syntax                                   4 -  3
  90. ________________________________________________________
  91.  
  92.  
  93. Befehlscodes und Adressierungsarten
  94.  
  95. Der Assembler übersetzt alle Befehlscodes und Adressierungsarten der 68000
  96. CPU. Zur Notation wird die Standard-Syntax von Motorola verwendet, wie sie
  97. in allen uns bekannten 68000-Lehrbüchern verwendet wird. Als Assembler-
  98. Könner dürfen Sie also ohne Einschränkungen drauflosprogrammieren.
  99.  
  100. Einige Besonderheiten sind allerdings zu beachten: Teilweise muß die genaue
  101. Anweisung  geschrieben  werden,  auch  wenn  der  Assembler  dies  eigentlich
  102. selbst korrigieren könnte. Bei Nichtbeachtung meldet der Compiler einen Fehler.
  103. Beispiele:
  104.  
  105.   CMPI     #x,D0       ; hier kann nicht CMP verwendet werden
  106.   CMPA.L  A0,A1       ; dito
  107.   MOVE.L  A2,A0      ; hier allerdings braucht nicht MOVEA stehen
  108.  
  109.   TST      0(A0,D0.W) ; Offset (hier: 0) und Größe (.W) sind notwendig
  110.  
  111.   CLR      (A2)        ; erzeugt indirekte Adr. ohne Offset
  112.   CLR      0(A2)       ; erzeugt indir. Adr. mit Offset
  113.   CLR      offs(A2)    ; ist offs Null, wird indir. Adr. ohne Offset erzeugt
  114.  
  115. Als Operanden können Sie unter anderem Konstanten, Variablen und Prozeduren
  116. verwenden, die Sie in Modula definiert haben. Allerdings müssen Sie etwas
  117. Rücksicht auf den Compiler und die Art, wie er seine Variablen und Prozeduren
  118. handhabt, nehmen. In den folgenden Abschnitten wird genau verraten, wie das
  119. funktioniert; hier schon einmal die wichtige Grundregel:
  120.  
  121. Auf globale Objekte (Variable und Prozeduren) wird mit absoluter Adressierung
  122. zugegriffen; auf lokale Objekte und Label mit relativer Adressierung (Label und
  123. Prozeduren: PC-relativ; Variable: Adreßregister mit Displacement). Trotz der
  124. Verwendung absoluter Adressen bleiben auch Module mit Assemblerteilen stets
  125. verschiebbar: Der Assembler legt automatisch die Informationen an, die zum
  126. Umrechnen der Adressen vor dem Starten des fertigen Moduls benötigt werden.
  127.  
  128. Bei Konstant-Ausdrücken verhält es sich exakt wie in CONST-Anweisungen.
  129. Auch  stehen  alle  Modula-Funktionen,  wie  SIZE  oder  CHR,  zur  Verfügung.
  130. Lediglich,  wenn  negative  Werte  angegeben  werden  sollen,  kann  unter
  131. Umständen der Ausdruck nicht mit dem Minuszeichen beginnen. Statt dessen
  132. muß  dann  eine  Null  vorangestellt  werden,  um  daraus  eine  Subtraktion  zu
  133. machen. Beispiele:
  134.  
  135.     MULU     #TSIZE (ArrayElement),D0
  136.     MOVE.B  #0-LENGTH (StringConst),D1
  137. 4.1  Assembler: Grundlagen und Syntax                                   4 -  4
  138. ________________________________________________________
  139.  
  140.  
  141. Label (Marken)
  142.  
  143. Als Label  (Sprungmarke)  können  alle  Bezeichner  verwendet  werden,  die  in
  144. Modula als Variablennamen etc. zulässig wären. Reserviert sind allerdings alle
  145. Opcode-Bezeichnungen der 68000 und auch die Modula-Schlüsselworte. (Mein
  146. Lieblingsfehler:  LOOP  ist  kein  erlaubtes  Label!)  Natürlich  dürfen  Sie  auch
  147. Bezeichner  benutzen,  die  außerhalb  des  Assemblerteils  bereits  anderweitig
  148. (z. B. als globale Variable) definiert sind - auch der Assembler unterstützt das
  149. Modula-Konzept der Lokalität. Dabei müssen Sie allerdings Rücksicht darauf
  150. nehmen, daß auch der Assembler nur einen Durchgang durch den Quelltext
  151. macht: In diesen Fällen muß das Label definiert sein, bevor Sie das erste Mal
  152. darauf Bezug nehmen; sonst nimmt der Assembler an, die bereits bekannte
  153. Bedeutung des Bezeichners sei gemeint. Normalerweise darf ein Label aber
  154. ohne weiteres vor seiner Definition benutzt werden; die Definition muß dann im
  155. gleichen Prozedur- bzw. Modulrumpf nachfolgen.
  156.  
  157. Labels darf ein Doppelpunkt hinten angestellt werden.
  158.  
  159. Labels dürfen - wie schon erwähnt - nur in PC-relativen Adressierungsarten
  160. benutzt werden. Beispiele für korrekte Anwendungen:
  161.  
  162.                LEA     Daten(PC), A0        ; Zugriff auf Tabellen
  163.                LEA     Ziel(PC), A1           ; gleich noch einer
  164.     Schleife:  MOVE   (A0)+, (A1)+          ; diese Marke endet mit ':'
  165.                BNE     Schleife               ; Bcc  und BSR sind möglich
  166.                RTS
  167.     Daten     DC.W   5, 4, 3, 2, 1, 0       ; der ':' muß aber nicht sein
  168.     Ziel       DS      20
  169.  
  170.  
  171. Relative Sprünge (Bcc und BSR)
  172.  
  173. Die  68000  kennt  bekanntlich  relative  Sprünge  mit  byte-  oder  wortlanger
  174. Angabe   der   Sprungweite.   Wird   dabei   eine   bereits   bekannte   Marke
  175. angesprungen,  so  verwendet  der  Assembler  automatisch  die  passende
  176. Argumentgröße.
  177.  
  178. Ist  dagegen  die  angesprungene  Marke  noch  undefiniert,  so  versucht  der
  179. Assembler im Normalfall, einen kurzen Sprung zu erzeugen. Stellt sich  bei
  180. Definition  der  Marke  heraus,  daß  sie  zu  weit  von  einem  solchen  Sprung
  181. entfernt ist, so ist eine Änderung der Sprunglänge nicht mehr möglich - ein
  182. Assemblerfehler wird angezeigt.
  183.  
  184. Um explizit die Erzeugung einer langen Sprungweite zu fordern, können Sie
  185. Bcc.W  schreiben.  Die  explizite  Forderung  eines  kurzen  Sprungs  ist  nicht
  186. erforderlich, aber durch Bcc.S oder Bcc.B möglich.
  187. 4.2  Einfache Assembleranwendungen                                     4 -  5
  188. ________________________________________________________
  189.  
  190.  
  191. 4.2  Einfache  Assembleranwendungen
  192.  
  193. Wenn  Sie  gelegentlich  ein  paar  maschinennahe  Operationen  in  Assembler
  194. codieren möchten, ohne gleich das halbe Innenleben Ihres Atari zu studieren,
  195. sind Sie in diesem Absatz richtig. Die folgenden Informationen genügen zur
  196. Realisierung vieler Anwendungen. Die Darstellung aller Möglichkeiten des Assem~
  197. blers bleibt jedoch dem Abschnitt 4.3 vorbehalten.
  198.  
  199.  
  200. Belegung der CPU-Register
  201.  
  202. Die Register der 68000 CPU dürfen Sie in Assemblerteilen nicht alle nach
  203. Belieben verwenden. Der vom Compiler erzeugte Code benutzt die Register als
  204. Zwischenspeicher. Einige Adreßregister haben besondere Funktionen und müssen
  205. daher sogar langfristig ihren Inhalt behalten.
  206.  
  207.        D0
  208.         ..     Zwischenspeicher
  209.        D2
  210.  
  211.        D3
  212.         ..     reserviert
  213.        D7
  214.  
  215.        A0     Zwischenspeicher
  216.        A1     Zwischenspeicher
  217.        A2     Zwischenspeicher
  218.        A3     reserviert
  219.        A4     reserviert
  220.        A5     reserviert
  221.        A6     reserviert - Zeiger auf aktuelle lokale Variable
  222.        A7     reserviert - Zeiger auf CPU-Stack
  223.  
  224. Zwischenspeicher können Sie in Assemblerteilen frei verwenden; diese Register
  225. behalten jedoch i.A. in Modula-Programmstücken nicht ihre Werte. Reservierte
  226. Register dürfen Sie gar nicht verändern!
  227.  
  228.  
  229. Zugriff auf globale Variable
  230.  
  231. Der Zugriff auf globale Variable ist wirklich einfach: Nur den Namen benutzen
  232. - fertig! Der Assembler ersetzt den Namen durch die absolute Adresse der
  233. Variablen. Variablen sind also immer als  Source-  oder  Destination-Adresse
  234. zulässig, wenn die 68000 absolute Adressierung erlaubt. Beispiele:
  235. 4.2  Einfache Assembleranwendungen                                     4 -  6
  236. ________________________________________________________
  237.  
  238.  
  239. CLR.W        MeinCardinal
  240. CMPI.B        #'x', MyChar
  241. SUBQ.L       #2, LongCount
  242. MOVE.W      j,k
  243. LEA           ArrayVar, A0
  244.  
  245. Etwas mehr müssen Sie naturgemäß tun, wenn die Variable nicht 1, 2 oder 4
  246. Bytes lang ist, also keine der 68000-Operandengrößen hat. Beispiele:
  247.  
  248.          RecTyp:       RECORD
  249.                            name:     ARRAY  0..9  OF CHAR;
  250.                            left, right: ADDRESS;
  251.                         END;
  252. VAR     MyReal:       LONGREAL;
  253.          Index:        CARDINAL;
  254.          MyString:     ARRAY  0..79  OF CHAR;
  255.          MyRecord:    RecTyp;
  256.  
  257. BEGIN
  258.   ASSEMBLER
  259.     LEA           MyReal, A0      ; Real-Variable auf den Parm-Stack bringen
  260.     MOVE.L       (A0)+,(A3)+
  261.     MOVE.L       (A0),(A3)+
  262.  
  263.     LEA           MyString, A0    ; D1 := MyString  Index 
  264.     MOVE.W      Index, D0
  265.     MOVE.B       0(A0,D0.W), D1
  266.   END
  267. END ...
  268.  
  269. Das letzte Beispiel zeigt, wie ein Stringelement erreicht  werden  kann.  Der
  270. Zugriff auf beliebige Felder wird in Abschnitt 4.3 erläutert.
  271.  
  272. Namen von RECORD-Feldern interpretiert der Assembler als den Abstand des
  273. Feldes vom RECORD-Anfang. Ein RECORD-Feld können Sie also adressieren,
  274. indem Sie die RECORD-Anfangsadresse in ein Register laden und den Feldnamen
  275. als 'Displacement' verwenden. Beachten Sie, daß trotzdem vor dem Feldnamen
  276. der Typname des Records stehen muß, damit der Assembler den Feldnamen
  277. überhaupt  erkennt  (es  sei  denn,  der  Assemblerteil  steht  innerhalb  einer
  278. WITH-Anweisung, die das benutzte RECORD eröffnet).
  279.  
  280.     LEA           MyRecord, A0        ; *** teste, ob MyRecord.left = NIL
  281.     TST.L         RecTyp.left (A0)
  282.     BEQ           empty
  283. 4.2  Einfache Assembleranwendungen                                     4 -  7
  284. ________________________________________________________
  285.  
  286.  
  287. Zugriff auf lokale Variable
  288.  
  289. Lokale  Variable  haben  keinen  festen  Speicherbereich,  sondern  werden  bei
  290. jedem Aufruf der zugehörigen Prozedur neu angelegt (wichtig für Rekursion!).
  291. Auf den  Beginn  des  Variablenbereichs,  der  zur  gerade  laufenden  Prozedur
  292. gehört, zeigt dann das Adreßregister A6. Alle lokalen Variablen müssen relativ
  293. zu diesem Register adressiert werden.
  294.  
  295. Der Assembler unterstützt diese Adressierung, indem er (wie bei Recordfeldern)
  296. lokale Variablennamen in ein Displacement umsetzt. Sie geben hinter diesem
  297. Displacement das Basisregister an (immer A6). Beispiele:
  298.  
  299. PROCEDURE asmDemo;
  300.   VAR  j, k, MeinCardinal: CARDINAL;
  301.         MyChar: CHAR;
  302.         LongCount: LONGCARD;
  303.         ArrayVar: ARRAY  0..9  OF INTEGER;
  304.   BEGIN
  305.      ASSEMBLER
  306.          CLR.W        MeinCardinal(A6)
  307.          CMPI.B        #'x', MyChar(A6)
  308.          SUBQ.L       #2, LongCount(A6)
  309.          MOVE.W      j(A6), k(A6)
  310.          LEA           ArrayVar(A6), A0
  311.          ...
  312.  
  313. Um lokale ARRAYs oder RECORDs zu adressieren, laden Sie deren Anfangs~
  314. adresse in ein Register (letztes Beispiel) und verfahren dann genauso weiter,
  315. wie oben für globale Variable beschrieben.
  316.  
  317. Schließlich  gibt  es  auch  die  (etwas  kompliziertere)  Möglichkeit,  in  lokalen
  318. Prozeduren Variablen der übergeordneten äußeren Prozedur zu  adressieren.
  319. Wenn Sie auch solche Zugriffe in Assemblerroutinen benötigen sollten, sehen
  320. Sie sich bitte den Abschnitt 'lokale Variablen' im folgenden Teil 4.3 an.
  321. 4.3  Assembler für Experten                                             4 -  8
  322. ________________________________________________________
  323.  
  324.  
  325. 4.3  Assembler  für  Experten
  326.  
  327. In  diesem  Abschnitt  erfahren  Sie,  wie  Sie  an  beliebige  Modula-Daten  und
  328. -Prozeduren von Assembler aus herankommen. Dazu müssen  wir  allerdings
  329. gelegentlich etwas 'ans Eingemachte' gehen und Details der Laufzeitorganisation
  330. von Modula-Programmen diskutieren. Darauf waren Sie sowieso neugierig? Um
  331. so besser...
  332.  
  333.  
  334. Pseudo-Opcodes
  335.  
  336. Der Assembler unterstützt folgende Pseudo-Opcodes, die vor allem zum Anlegen
  337. von Tabellen dienen:
  338.  
  339. Define Constant
  340.      legt Konstanten als Byte, Wort oder Langwort im Code ab.
  341.     DC.B     $1001, $1002, 'a'            ; legt LowBytes ab ($01,$02,$61)
  342.     DC.W     $A, $B, 12, 13
  343.     DC.L      WriteLn, WriteString        ; legt Prozeduradressen ab
  344.     DC.D     1.5, -0.3333333             ; legt Longreal-Konstanten ab
  345.  
  346. Define Storage
  347.     reserviert Platz im Code. Meist ist die Verwendung von Modula-Variablen
  348.     einer DS-Anweisung vorzuziehen!
  349.     DS       100                          ; 100 Byte freihalten
  350.  
  351. ASCII Constant
  352.     legt Zeichenfolge im Code ab (ohne Endmarke)
  353.     ASC      "Hallo"
  354.  
  355. ASCII with Zero
  356.     legt Zeichenfolge mit Endmarke 0.B ab (Stringformat).
  357.     ACZ      "Hier folgt eine Null ->"    ; so sehen auch Modula-Strings aus
  358.  
  359. ASCII with Length (Pascal-like String)
  360.     legt Zeichenfolge mit führendem Längenbyte ab.
  361.     STR      "Pascal"                  ; genauso wie: DC.B 6,'P','a','s','c','a','l'
  362.  
  363. Synchronisieren
  364.     erzeugt ein Null-Byte, falls der PC ungerade ist. Anzuwenden, wenn z.B.
  365.     nach Byte-Tabellen Befehlscodes folgen.
  366.     SYNC                                  ; hat keine Argumente
  367. 4.3  Assembler für Experten                                             4 -  9
  368. ________________________________________________________
  369.  
  370.  
  371. Wenn Sie unter den Pseudo-Opcodes Anweisungen zur Definition von Labels
  372. auf bestimmte Adressen vermissen, liegt das nicht daran, daß wir die verges~
  373. sen hätten - die Deklaration von Konstanten in Modula (CONST-Deklaration)
  374. sowie die Deklaration von Variablen auf festen Adressen  (VAR  a   $1234 )
  375. bietet ja genau diese Funktionen.
  376.  
  377.  
  378. Belegung der CPU-Register
  379.  
  380. Die Register der 68000 CPU dürfen Sie in Assemblerteilen nicht alle nach
  381. Belieben verwenden. Der vom Compiler erzeugte Code benutzt die Register als
  382. Zwischenspeicher. Einige Adreßregister haben besondere Funktionen und müssen
  383. daher sogar langfristig ihren Inhalt behalten.  Diese  speziellen  Adreßregister
  384. sind teilweise auch für den Assemblerprogrammierer von Nutzen; ihre Funktion
  385. wird hier genauer erläutert.
  386.  
  387.        D0
  388.         ..     Zwischenspeicher
  389.        D2
  390.  
  391.        D3
  392.         ..     reserviert - für Register- und FOR-Variable
  393.        D7
  394.  
  395.        A0     Zwischenspeicher
  396.        A1     Zwischenspeicher
  397.        A2     Zwischenspeicher
  398.        A3     reserviert - Zeiger auf Parameter-Stack
  399.        A4     reserviert - für Register-Variable und WITH
  400.        A5     reserviert
  401.        A6     reserviert - Zeiger auf aktuelle lokale Variable
  402.        A7     reserviert - Zeiger auf CPU-Stack
  403.  
  404. Zwischenspeicher  können  Sie  in  Assemblerteilen  verwenden;  diese  Register
  405. behalten jedoch im allgemeinen in Modula-Programmstücken nicht ihre Werte.
  406.  
  407. Der Zeiger auf den CPU-Stack (A7) ist Ihnen als Assembler-Programmierer
  408. bereits bekannt. Wie von der Architektur der 68000 vorgegeben, benutzt der
  409. Compiler diesen Stack zum Ablegen von Rücksprungadressen  bei  Unterpro~
  410. grammadressen; außerdem werden die lokalen Variablen für aufgerufene Proze~
  411. duren auf dem CPU-Stack angelegt. (Die 68000 unterstützt dies durch das
  412. Opcode-Paar  LINK/UNLK).  Auch  in  einigen  anderen  Situationen  (FOR-  und
  413. WITH-Anweisungen) wird Platz auf  dem  CPU-Stack  benötigt.  Dieser  Stack
  414. 'wächst' bekanntlich von oben nach unten; beim Aufstapeln neuer Daten wird
  415. A7 erniedrigt (dekrementiert).
  416. 4.3  Assembler für Experten                                             4 - 10
  417. ________________________________________________________
  418.  
  419.  
  420. Der Zeiger auf den Parameter-Stack (A3) verwaltet einen  weiteren  Stack.
  421. Dieser dient speziell zur Übergabe von Parametern (und evtl. Ergebnissen) von
  422. Prozeduren.  Auch  Zwischenergebnisse  bei  der  Auswertung  von  Ausdrücken
  423. werden dort abgelegt. Der Parameter-Stack wächst von unten nach oben, und
  424. zwar direkt dem CPU-Stack entgegen: Im Arbeitsspeicher eines aufgerufenen
  425. Programms steht A7 zunächst am oberen, A3 am unteren Rand. Dadurch wird
  426. der Arbeitsspeicher je nach Bedarf von beiden Stacks beansprucht.
  427.  
  428. Das Register zur Verwaltung der lokalen Variablen (A5) funktioniert viel einfacher,
  429. als sein Name befürchten läßt. Um Rekursion zu ermöglichen, muß bei jedem
  430. Aufruf einer Prozedur neuer Platz für die lokalen Variablen organisiert werden.
  431. (Vielleicht ruft sich die Prozedur ja gerade rekursiv selbst auf; dann werden
  432. die vorher benutzten Variablen nach der Rückkehr wieder gebraucht!)
  433.  
  434. Dazu bietet die 68000-CPU die LINK-Instruktion an: LINK A5, #Platz rettet
  435. A5 auf dem CPU-Stack, kopiert den Stackpointer nach A5 und subtrahiert
  436. <Platz> vom Stackpointer. Presto - schon zeigt der Stackpointer auf <Platz>
  437. Bytes freien RAM, den wir für die Variablen benutzen wollen. Am Ende der
  438. Prozedur genügt ein UNLK, um A5 und den Stackpointer wieder in den Zustand
  439. vor dem letzten LINK zu versetzen. - Das Ganze funktioniert natürlich auch
  440. mit anderen Registern als A5; der Compiler hat sich aber auf dieses Register
  441. festgelegt und hofft sehr, daß Sie es in Ihren Assemblerteilen immer schön
  442. heil lassen.
  443.  
  444. Bleibt noch der Zeiger auf die aktuellen lokalen Variablen (A6) zu erwähnen.
  445. Wie Sie im vorigen Absatz gelesen haben, zeigt  nach  der  LINK-Instruktion
  446. zunächst A7 auf den gerade reservierten Platz. Da sich A7 aber (etwa  in
  447. FOR-Schleifen) verschieben wird, benötigen wir einen anderen, stabilen Zeiger
  448. auf die Variablen. Der Compiler reserviert hierfür A6 und erzeugt an jedem
  449. Prozeduranfang Code, der A6 auf die Variablen zeigen läßt.
  450.  
  451.  
  452. Zugriff auf Modula-Konstanten
  453.  
  454. In Modula definierte Konstanten können Sie ohne Einschränkungen als Operan~
  455. den verwenden. Sowohl die Benutzung als 'Immediate'-Daten (hinter '#')  als
  456. auch als globale Adressen oder relative Displacements ist möglich. Beispiele:
  457.  
  458. MOVE.L       #Konstante, D0
  459. LEA           AdressKonst, A0
  460. MOVE.L       D0, AdressKonst
  461. LEA           Offset(A0), A2
  462. 4.3  Assembler für Experten                                             4 -  11
  463. ________________________________________________________
  464.  
  465.  
  466. Zugriff auf globale Modula-Variable, speziell ARRAYs
  467.  
  468. Das Wichtigste über globale Variablen haben wir schon in Kapitel 4.2 erläutert
  469. (Abschnitt 'Zugriff auf globale Modula-Variable'); diese Grundlagen wollen wir
  470. hier  nicht  wiederholen.  Schuldig  geblieben  sind  wir  aber  die  Beschreibung
  471. allgemeiner ARRAY-Zugriffe.
  472.  
  473. Die Anfangsadresse des ARRAYs wird in ein Adreßregister geladen. Ist die
  474. untere Grenze des Indexbereiches nicht Null, so müssen Sie vom Indexwert
  475. zunächst diese Untergrenze subtrahieren - das erste Element des Feldes steht
  476. also immer direkt am Beginn des Feldplatzes. Der so korrigierte Index wird
  477. dann mit der Byte-Größe der Feldelemente multipliziert. Bei der Bestimmung
  478. der Elementgrößen hilft Anhang A.3. Beispiel:
  479.  
  480. LEA           MyArray, A0         ; MyArray  Index  auf den CPU-Stack bringen
  481. MOVE.W      Index, D0
  482. SUB.W        #LowBound, D0       ; Untergrenze vom Index abziehen
  483. LSL.W        #3, D0               ; Index mit Elementgröße (2^3) multiplizieren
  484. MOVE.L       0(A0,D0.W), -(A7)  ; erst die vorderen 4 Bytes auf den Stack...
  485. MOVE.L       4(A0,D0.W), -(A7)  ; ... dann noch den Rest
  486.  
  487. Ist das Produkt aus Elementlänge und Index-Untergrenze kleiner als 128, dann
  488. ist eine vereinfachte Version des Feldzugriffs möglich: Statt die Untergrenze
  489. vom  Index  zu  subtrahieren,  können  Sie  auch  das  Displacement  beim
  490. eigentlichen  Zugriff  korrigieren.  Beispiel  für  den  häufigen  Fall,  daß  die
  491. Untergrenze 1 ist (MyArray: ARRAY  1..n  OF CARDINAL):
  492.  
  493. LEA           MyArray, A0          ; *** Ziel := MyArray Index 
  494. MOVE.W      Index, D0
  495. ADD.W        D0, D0               ; Elementlänge ist 2 Byte
  496. MOVE.W      -2(A0,D0.W), Ziel   ; Zugriff mit Korrektur der Untergrenze
  497.  
  498.  
  499. Zugriff auf lokale Variable
  500.  
  501. Auch zu diesem Thema finden Sie die grundlegenden Informationen in Kapitel
  502. 4.2. Im Folgenden werden Sie zusätzlich erfahren, wie in lokalen Prozeduren
  503. auf die Variablen übergeordneter Prozeduren zugegriffen wird.
  504.  
  505. Auch diese Variablen sind noch nicht global, werden also zur Laufzeit dynamisch
  506. angelegt. Um sie wiederfinden zu können, baut der Compiler zur Laufzeit eine
  507. Zeigerkette auf: In lokalen Prozeduren zeigt Adreßregister A6 gar nicht direkt
  508. auf die aktuellen Variablen der laufenden Prozedur (da haben wir oben  ein
  509. bißchen vereinfacht). Unter der Adresse (A6) finden Sie zunächst einen Zeiger
  510. auf die Variablen der übergeordneten Prozedur; erst dahinter (Adresse 4(A6))
  511. beginnen die eigenen Variablen. Bei der Adressierung über den Variablennamen
  512. wird dieser Offset natürlich automatisch berücksichtigt.
  513. 4.3  Assembler für Experten                                             4 - 12
  514. ________________________________________________________
  515.  
  516.  
  517. Damit ist Ihnen sicher schon klar, wie Sie an die äußeren Daten herankommen.
  518. Das folgenden Beispiel beseitigt hoffentlich die letzten Zweifel:
  519.  
  520. PROCEDURE außen;
  521.     VAR  aVar: CARDINAL;
  522.  
  523.     PROCEDURE innen;
  524.        VAR iVar: CARDINAL;
  525.        BEGIN
  526.           ASSEMBLER
  527.              MOVE.L  (A6), A0        ; A0 zeigt auf Variablen von 'außen'
  528.              ADDQ    #1, aVar(A0)    ; jetzt ist aVar erreichbar
  529.              MOVE    aVar(A0), iVar(A6)
  530.           END
  531.        END innen;
  532.     ..
  533.     END außen;
  534.  
  535. Angenommen, in diesem Beispiel ist auch 'außen' lokal zu einer dritten Prozedur
  536. 'ganzAußen'  deklariert,  und  an  deren  Variablen  wollen  Sie  nun  von  'innen'
  537. herankommen - dann brauchen Sie die Zeigerkette natürlich nur eine Stufe
  538. weiter zu verfolgen, denn auch 'außen' hat vor seinen Variablen einen Zeiger
  539. auf die übergeordneten Daten. Also:
  540.  
  541.      MOVE.L  (A6), A0    ; A0 zeigt auf Variablen von 'außen'
  542.      MOVE.L  (A0), A0    ; A0 zeigt auf Variablen von 'ganzAußen'
  543.  
  544. Da wir vorhaben, einige Optimierungen an der Code-Erzeugung des Compilers
  545. vorzunehmen,  weisen  wir  vorsorglich  darauf  hin,  daß  Zugriffe  auf  lokale
  546. Variablen innerhalb von WITH-Anweisungen nicht durchgeführt werden sollen.
  547. Statt dessen  ist  besser  eine  lokale  Prozedur  anzulegen,  die  innerhalb  der
  548. WITH-Anweisung aufgerufen werden kann und  in  der  dann  die  Assembler-
  549. Zugriffe, wie oben beschrieben, auf die lokalen Variablen erfolgen können.
  550. 4.3  Assembler für Experten                                             4 - 13
  551. ________________________________________________________
  552.  
  553.  
  554. Fehlerprüfungen vom Assembler
  555.  
  556. Der Assembler führt ein paar Überwachungen durch, auf die wir Sie vielleicht
  557. hinweisen sollten:
  558.  
  559. Führen Sie einen Datenzugriff auf eine Modula-Variable  durch  (z.B.  MOVE,
  560. ADD, nicht jedoch LEA), prüft der Compiler, je nachdem, ob dies sinnvoll sein
  561. kann. So macht es keinen Sinn, MOVE var,D0 zu programmieren, wenn die
  562. Variable  lokal  ist.  Ebenso  unsinnig  wäre  der  Zugriff  mit  einem  Adreß-
  563. Displacement  bei  einer  globalen  Variablen.  In  solchen  Fällen  meldet  der
  564. Assembler den Fehler Logisch falsche Adressierung. Die selbe Fehlermeldung
  565. können Sie auch bei anderen, sinnlosen Verwendungen von Modula-Bezeichnern
  566. erwarten. Allerdings meckert er nicht, wenn Sie beim Zugriff auf eine lokale
  567. Variable das falsche Adreßregister benutzen (also nicht A6), Sie könnten A6 ja
  568. in das verwendete Register kopiert haben.
  569.  
  570.  
  571. Aufruf von globalen Modula-Prozeduren
  572.  
  573. Um eine global unter Modula deklarierte Prozedur aufzurufen, genügt ein JSR
  574. ProzedurName.  Der  Compiler  erzeugt  daraus  einen  Aufruf  mit  absoluter
  575. Adressierung (Sie erinnern sich an die Grundregel aus Abschnitt 4.1?). Das
  576. funktioniert ohne weiteres auch für importierte Prozeduren, z.B. JSR WriteLn.
  577.  
  578. Wenn eine Prozedur Parameter erwartet, liegt es beim Aufruf aus Assembler~
  579. programmen in Ihrer Verantwortung, diese richtig bereitzustellen. Das geht so:
  580.  
  581. * Vor dem Aufruf einer Prozedur werden alle Parameter in der Reihenfolge
  582. ihrer Deklaration im Prozedurkopf auf den Parameter-Stack gebracht.
  583.  
  584. * Bei Wert-Parametern ('call by value') wird eine Kopie des Wertes übergeben;
  585. bei  VAR-Parametern  ('call  by  reference')  übergeben  Sie  die  Adresse  der
  586. Variablen.
  587.  
  588. * Wichtig bei Wert-Übergaben: Der Parameter-Stackpointer muß nach jedem
  589. Parameter  synchronisiert  werden!  Ist  ein  Parameter  eine  ungerade  Anzahl
  590. Bytes lang, anschließend A3 um 1 Byte erhöhen!
  591.  
  592. *  An  Open  ARRAY-Parameter  wird  (bei  Wert-  und  bei  VAR-Parametern)
  593. immer die Adresse und anschließend der HIGH-Wert (als Wort) übergeben. Bei
  594. Übergabe an Open ARRAY Wert-Parameter muß eine Kopie des Parameters
  595. erzeugt und übergeben werden, damit die aufgerufene Prozedur das Original
  596. nicht verändern kann.
  597. 4.3  Assembler für Experten                                             4 - 14
  598. ________________________________________________________
  599.  
  600.  
  601. * Die aufgerufene Prozedur sorgt für das Abräumen der Parameter vom A3-Stack.
  602. Funktions-Prozeduren legen vor der Rückkehr statt dessen ihren Ergebniswert
  603. dort ab.
  604.  
  605. Noch nicht alles klar? Dann helfen sicher einige Beispiele:
  606.  
  607. MODULE AsmBsp;
  608.  
  609. FROM SYSTEM IMPORT ASSEMBLER;
  610.  
  611. VAR MeinCardinal : CARDINAL;  (* 16 Bit: 1 Word *)
  612.      MeinString : ARRAY  0..39  OF CHAR;
  613.      MeinReal, DeinReal : LONGREAL;
  614.  
  615. BEGIN
  616.   ASSEMBLER
  617.      ; WriteCard (c: LONGCARD; space: CARDINAL)
  618.     MOVEQ.L     #0, D0                      ; Long-Wert in D0 löschen
  619.     MOVE.W      MeinCardinal, D0             ;Wert von MeinCardinal
  620.     MOVE.L       D0, (A3)+                   ; und auf den Stack damit
  621.     MOVE.W      #8, (A3)+                    ;Feldbreite für Ausgabe
  622.     JSR           WriteCard                   ;Ausgeben
  623.  
  624.      ; ReadCard (VAR c: CARDINAL)
  625.     MOVE.L       #MeinCardinal, (A3)+        ; Adresse von MeinCardinal
  626.     JSR           ReadCard                    ;Einlesen
  627.  
  628.      ; ReadString (VAR s: ARRAY OF CHAR)
  629.     LEA           MeinString, A0              ;ein  ARRAY  0..39  OF CHAR
  630.     MOVE.L       A0, (A3)+                    ;Adresse des Strings
  631.     MOVE.W      #SIZE(MeinString)-1,(A3)+   ;HIGH-Wert
  632.     JSR           ReadString
  633.  
  634.      ; sin (x: REAL): REAL
  635.     LEA           MeinReal, A0                ; Realzahl auf den Stack
  636.     MOVE.L       (A0)+, (A3)+                ; ..
  637.     MOVE.L       (A0), (A3)+                  ; ..
  638.     JSR           sin                           ; sin (MeinReal) berechnen
  639.     LEA           DeinReal, A0                 ; Real-Ergebnis vom Stack abholen
  640.     MOVE.L       -(A3), 4(A0)                 ; ..
  641.     MOVE.L       -(A3), (A0)                  ; ..
  642. 4.3  Assembler für Experten                                             4 - 15
  643. ________________________________________________________
  644.  
  645.  
  646. Aufruf von lokalen Modula-Prozeduren
  647.  
  648. Keine Sorge - alles, was Sie eben über Parameterübergaben gelernt haben, gilt
  649. für lokale Prozeduren ganz genau wie für globale. Einen Unterschied gibt es
  650. nur beim Aufruf selbst: Lokale Prozeduren werden PC-relativ adressiert, also
  651. durch BSR ProzedurName.
  652.  
  653. Vor dem Aufruf müssen Sie allerdings noch eine weitere Information bereitstel~
  654. len: einen Zeiger auf den nächsthöheren erreichbaren Variablenbereich, der im
  655. Datenregister D2 zu übergeben ist. Welcher Bereich sichtbar ist, hängt von
  656. der Deklarations-Hierarchie von Aufrufer und aufgerufener Prozedur ab. Das
  657. folgende  Beispiel  illustriert  die  beiden  gängigen  Anordnungen  und  zeigt  die
  658. Bereitstellung des Zeigers:
  659.  
  660. PROCEDURE außen;
  661.  
  662.    PROCEDURE innen1;
  663.    END innen1;
  664.  
  665.    PROCEDURE innen2;
  666.  
  667.        PROCEDURE ganzInnen;
  668.        END ganzInnen;
  669.  
  670.        BEGIN (* innen2 *)
  671.           ..
  672.           MOVE.L   A6, D2    ; Aufrufer ruft zu ihm lokale Prozedur:
  673.           BSR       ganzInnen  ; Variablen des Aufrufers sichtbar
  674.           ..
  675.           MOVE.L   (A6), D2   ; Aufrufer ruft Prozedur auf gleicher Ebene:
  676.           BSR       innen1      ; die Variablen, die der Aufrufer sieht,
  677.           ..
  678.                                  ;  sieht auch der Gerufene
  679.           MOVE.L   (A6), D2   ;  rekursive Aufrufe rufen ebenfalls
  680.           BSR      innen2      ;  eine Prozedur auf gleicher Ebene
  681.            ..
  682.       END innen2;
  683.  
  684.    BEGIN
  685.       ..
  686.    END außen;
  687.  
  688.  
  689. Nochmal   in   Kurzfassung:   Parameter   bereitstellen   (wie   unter   "globale
  690. Prozeduren" beschrieben); je nach Aufruf-Hierarchie passenden Zeiger nach
  691. D2 holen; Aufruf mit BSR.
  692. 4.3  Assembler für Experten                                             4 - 16
  693. ________________________________________________________
  694.  
  695.  
  696. Übernahme von Parametern (Link-Option)
  697.  
  698. Normalerweise sorgt der Compiler dafür, daß zu Beginn einer Prozedur die
  699. Parameter in lokale Variablen übernommen werden. In Assembleranweisungen
  700. können Sie - wie auch unter Modula - die Parameter genau wie lokale Variablen
  701. über ihre Namen adressieren.
  702.  
  703. Wenn Sie einen kompletten Prozedurrumpf in Assembler implementieren wollen,
  704. kann das Umkopieren vom Parameter-Stack in lokale Variablen (und von dort
  705. später in CPU-Register) ineffizient sein. Oft ist es wünschenswert, die Parame~
  706. ter direkt vom Stack in CPU-Register zu übernehmen. In diesem Fall können
  707. Sie das Umkopieren  durch  den  Compiler  unterdrücken,  indem  Sie  vor  den
  708. Prozedurrumpf die Compileroption (*$ L- *) setzen (s. 3.3). Dann gilt:
  709.  
  710. * Die Parameter stehen so auf dem Stack (A3), wie oben im Abschnitt 'Aufruf
  711. globaler Modula-Prozeduren' beschrieben wird. Die aufgerufene Prozedur (also
  712. Ihr Assemblerprogramm)  ist  für  das  Abräumen  der  Parameter  vom  Stack
  713. verantwortlich.
  714.  
  715. * Funktions-Prozeduren müssen vor der Rückkehr den Ergebnis-Wert auf den
  716. Parameterstack bringen.
  717.  
  718. * Prozeduren, die unter (*$ L- *) übersetzt werden,  dürfen  keine  lokalen
  719. Variablen haben! Falls Sie außer den CPU-Registern zusätzlichen Speicherplatz
  720. benötigen, sollten Sie ihn auf dem CPU-Stack (A7) oder mittels DS-Anweisungen
  721. selbst anlegen.
  722.  
  723. Als Beispiel hier noch einmal der 'Bit Reversal'-Algorithmus aus 4.1, diesmal als
  724. komplette Funktion, wie sie in einem FFT-Modul (Fast Fourier Transformation)
  725. Verwendung finden könnte:
  726.  
  727. PROCEDURE BitRev (c: CARDINAL): CARDINAL;
  728.     (*$ L- *)
  729.     BEGIN
  730.          ASSEMBLER
  731.                MOVE   -(A3),D0    ; hole Parameter
  732.                MOVEQ #15, D1      ; 16 Bit sind umzukehren
  733.          lp    LSR     #1, D0       ; schiebe Bit ins eXtend-Flag
  734.                ROXL   #1, D2       ; .. und von dort ins Zielregister
  735.                DBF     D1, lp        ; das Ganze 16 mal
  736.                MOVE  D2,(A3)+    ; schreibe Ergebnis auf den Stack
  737.          END;
  738.     END BitRev;
  739.     (*$ L= *)
  740.